As you've seen, the basic concepts of ASP programming are simple, especially if you're already familiar with scripting and ADO programming. To build complete and effective ASP applications, you only need to learn how to use the ASP object model, which isn't that complex, at least compared with other object hierarchies you've mastered.
The ASP object model, which is outlined in Figure 20-5, consists of just six main objects, which I'll describe in depth in the next sections. As you can see in the figure, this model isn't a hierarchy because there are no direct relationships among the six objects.
The Request object represents the data coming from the client browser. It exposes six properties (QueryString, Form, ServerVariables, Cookies, ClientCertificate and TotalBytes), the first five of which are actually collections, and one method (BinaryRead). All the properties are read-only, which makes sense because the ASP running on the server has no way to affect the data the client sends.
Figure 20-5. The ASP object model.
To fully understand the Request object's features, you need to know how data is sent from the client. An HTML form in the client browser can send data in two distinct ways: using the GET method or using the POST method. The method chosen depends on the METHOD attribute of the <FORM> tag. For example, the following form sends some values using the GET method. (It is part of the form shown in Figure 20-6.)
<H1>Send data through the GET method</H1> <FORM ACTION="http://www.yourserver.com/Get.asp" METHOD=get NAME=FORM1><P> Your Name: <INPUT name=txtUserName > Your Address: <INPUT name=txtAddress > Your City: <INPUT name=txtCity > <INPUT NAME=reset1 TYPE=reset VALUE=Reset> <INPUT NAME=submit1 TYPE=submit VALUE=Submit> </FORM> |
Figure 20-6. A form that sends a few values over the Internet using the GET method.
The value of the ACTION attribute is the URL of another page that will be executed and will receive the values in the three TextBox controls. When the user clicks the Submit button of a form that uses the GET method, the values of the controls in the form are appended to the URL specified in the ACTION parameter:
http://www.yourserver.com/Get.asp?txtUserName=Francesco+Balena&txtCity=Bari&txtCountry=Italy&submit1=Submit |
Notice that a question mark is appended immediately after the URL and that ampersand characters are used as delimiters for the controlname=value pairs sent to the server. When the user clicks the Submit button, the above string will appear in the browser's Address combo box when the browser finishes downloading the target page. Once you understand how the URL is built, nothing prevents you from building it yourself, for example, by using a client-side VBScript routine that fires when a button is clicked. If you choose to do so, you'll need to add the expected delimiters yourself, and you'll also have to replace all the spaces and other special characters with valid symbols:
<SCRIPT LANGUAGE=VBScript> ' This is a client-side script routine. Sub btnSendData_onclick() url = "http://www.yourserver.com/get.asp?" url = url & "txtUserName=" & Replace(Form1.txtUserName.Value, " ", "+") url = url & "&txtCity=" & Replace(Form1.txtCity.Value, " ", "+") url = url & "&txtCountry=" & Replace(Form1.txtCountry.Value, " ", "+") Window.Navigate url End Sub </SCRIPT> |
As you can see, this approach requires more code, but it's also more flexible than sending data via the GET method of a form because the client-side script can validate and preprocess the data before sending it. At times, you don't even need a script. For example, you can have two or more hyperlinks on the same page, and all of them can point to the same URL in spite of your having appended different values to that URL:
<A href="http://www.yourserver.com/get.asp?request=titles> Show me the titles</A> <A href="http://www.yourserver.com/get.asp?request=authors> Show me the authors</A> |
Building a URL via code does have one drawback, however: a few characters have a special meaning when they appear in a URL. For example, you should replace all spaces with + symbols, and you should use % characters only as escape characters. For additional information about characters with special meanings, see the section "Encoding HTML Text and URLs" later in this chapter.
The GET method for sending data has two drawbacks. First, because of limitations of the HTTP protocol, a browser can send only about 1,000 characters with the URL, so the data might be truncated. Second, data is sent as text over the Internet, which makes intercepting it easier. (This second problem is less serious if you build the URL yourself because you can encrypt the data being sent.)
If you want to work around the first problem and make your data harder to get at, you can use the POST method of sending data. In this method, the data is sent in the HTTP header, and the user doesn't see anything in the browser's Address combo box. To send data through the POST method instead of the GET method, you have to change only the value of the METHOD attribute of the <FORM> tag:
<FORM ACTION="http://www.yourserver.com/Get.asp" METHOD=post NAME=FORM1><P> |
On the client side, the only difference between the two methods of sending data is the value of the METHOD attribute, but the code in the ASP page that has to process the incoming data is completely different in the two cases. When data has been sent through the GET method (or by manually appending data to the URL), you can retrieve it by using the Request object's QueryString property. This property has a dual nature; it can work both as a regular property and as a collection. When used as a collection, you can pass it the name of a control on the form and it will return the value of that argument. What follows is the Get.asp page that retrieves the data being passed by the form:
<H1>This is what the ASP script has received:</H1> <B>The entire Request.QueryString: </B> <% = Request.QueryString %> <P><I>The string can be broken as follows:</I><P> <B>UserName:</B> <% = Request.QueryString("txtUserName") %> </BR> <B>City:</B> <% = Request.QueryString("txtCity") %> </BR> <B>Country:</B> <% = Request.QueryString("txtCountry") %> </BR> |
If the argument passed to the QueryString doesn't match the name of a control sent on the URL, the property returns an empty string, without raising an error. You can also take advantage of the collection nature of the QueryString property and enumerate all the values it contains using a For Each& Next loop:
<% For Each item In Request.QueryString %> <B><% = item %></B> = <% Request.QueryString(item) %><BR> <% Next %> |
When the client sends data through the POST method, the QueryString property returns an empty string and you have to retrieve data using the Form collection:
<B>UserName:</B> <% = Request.Form("txtUserName") %> </BR> |
You can also retrieve the values in all the controls in the form using a For Each& Next loop:
<% For Each item In Request.Form %> <B><% = item %></B> = <% Request.Form(item) %><BR> <% Next %> |
When working with the controls on a form, you have to account for controls that have the same name. You need to consider two cases. When the controls with the same name are radio buttons and when they are something else. When the controls are radio buttons, the rule is simple: Only the single radio button control the user has selected is returned in the QueryString or the Form property. For example, if you have the following controls in your form
<INPUT TYPE=radio NAME=optLevel VALUE=1>Beginner <INPUT TYPE=radio NAME=optLevel VALUE=2>Expert |
the script statement Request.QueryString("optLevel")—or Request.Form("optLevel"), if the form sends data with the POST method—will return 1 or 2, depending on the selected control.
When you have multiple controls with the same name and a type other than Radio, the QueryString of the Form collection will include a subcollection that holds all the controls whose value isn't empty. This detail is important to remember: If the form contains two controls named chkSend, the Request.QueryString("chkSend") or the Request.Form("chkSend") element might contain zero, one, or two elements, depending on how many check boxes the user has flagged:
<INPUT TYPE=checkbox NAME=chkSend VALUE="Catalog">Send me your catalog <INPUT TYPE=checkbox NAME=chkSend VALUE="News">Send me your newsletter |
If you have two or more check boxes with the same name, you can distinguish among the many cases using the count property, as follows:
<% If Request.QueryString("chkSend").Count = 1 Then %> <B>Send:</B> <% = Request.QueryString("chkSend") %><BR> <% Else For i = 1 To Request.QueryString("chkSend").Count %> <B>Send:</B> <% = Request.QueryString("chkSend")(i) %><BR> <% Next End If %> |
The preceding source code is meant for use with the GET method. The source code for the server-side script that reads data sent using the POST is similar, except it will use the Form collection instead of the QueryString collection. The companion CD includes two examples of HTM pages that send data to an ASP page: one using the GET method and another using the POST method. An example of a result from the ASP page is shown in Figure 20-7.
Figure 20-7. This page has been dynamically created by an ASP server-side script and sent back to the client; notice the URL in the browser's Address combo box.
Each request from the client browser carries a lot of information in the HTTP header, including critical information about the user, the client browser, and the document itself. You can access this information using the ServerVariables collection of the Request object. To test this capability, you can write a short server-side script that lists the contents of this collection. The following code is an excerpt from the ServerVa.asp file on the companion CD:
<H1>The ServerVariables collection</H1> <TABLE BORDER=1 WIDTH = 90%> <TR> <TH>Variable</TH> <TH>Value</TH> </TR> <% For Each item In Request.ServerVariables %> <TR> <TD><B> <% = item %> </B></TD> <TD> <% = Request.ServerVariables(item) %> </TD> </TR> <% Next %> </TABLE> |
A few items in this collection are particularly useful. For example, you can use the following code to determine the method the page used to send data:
<% Select Case UCase(Request.ServerVariables("REQUEST_METHOD")) Case "GET" ' Data is being sent through the GET method. Case "POST" ' Data is being sent through the POST method. Case "" ' No data is being sent from the client. End Select %> |
Another important item in this collection is HTTP_USER_AGENT , which holds the name of the client browser, thereby permitting you to filter out unsupported HTML statements. For example, you might return DHTML code to Internet Explorer 4 or later but stick to standard HTML code in all other cases:
<% Supports_DHTML = 0 ' Assume that the browser doesn't support DHTML. info = Request.ServerVariables("HTTP_USER_AGENT") If InStr(info, "Mozilla") > 0 Then ' This is a Microsoft Internet Explorer browser. If InStr(info, "4.") > 0 Or InStr(info, "5.") > 0 Then ' You can safely send DHTML code. Supports_DHTML = True End If End If %> |
Other items of interest in the ServerVariables collection are APPL_PHYSICAL_PATH (the physical path of the application), SERVER_NAME (the name or IP address of the server), SERVER_PORT (the port number used on the server), SERVER_SOFTWARE (the name of the Web server software, for example, Microsoft-IIS/4.0), REMOTE_ADDR (the client's IP address), REMOTE_HOST (the client's host name), REMOTE_USER (the client's user name), URL (the URL of the current page. which might be useful for referencing other files on the server), HTTP_REFERER (the URL of the page that contains the link the user clicked to get to the current page), HTTPS (returns on if you're using a secure protocol), and HTTP_ACCEPT_LANGUAGE (a list of natural languages the client browser supports).
Cookies are pieces of information stored as individual files on the client machine. The browser sends this information to the server with each request, and an application running on the server can use the cookies to store data about that specific client. This method of storing data is necessary because HTTP is a stateless protocol and therefore the server can't associate a given set of variables with a given client. Actually, the server can't even find out whether this is the first time the client is accessing one of its pages. To obviate this problem, the server can send a cookie to the client, which will store the cookie and resubmit it to the server at the next request. Depending on the expiration date set at creation time, the cookie can expire at the end of the current session or at a given date and time. or it can never expire. For example, Web servers that let their clients customize the home page often use cookies that never expire.
From within ASP code, you can access cookies in two different ways: as a collection of the Request object or as a collection of the Respond object. The difference between the two modes of access is important. The Request.Cookies collection is read-only because the server is just accepting a request from the client and can only examine the cookies that come with it. Conversely, you can create and send new cookies to a client only through the Response.Cookies collection (which is explained in the section "The Response Object" later in this chapter). You can retrieve the contents of a cookie within an ASP script by using the following syntax:
User Preference: <% = Request.Cookies("UserPref") %> |
You can also use a For Each& Next loop to enumerate all the cookies sent by the client. But you must account for an added difficulty. the fact that a cookie can have multiple values, which are held in a secondary collection. You can test whether a cookie has multiple values by checking its HasKeys Boolean property. The following code prints the contents of the Cookies collection and all its subcollections:
<% For Each item In Request.Cookies If Request.Cookies(item).HasKeys = 0 Then %> <% = item %> = <% Request.Cookies(item) %> <% Else For Each subItem In Request.Cookies(item) %> <% = item & "(" & subItem & ")" %> = <% Request.Cookies(item)(subItem> %> <% Next End If Next %> |
The Request object supports two more properties and one method. The ClientCertificate collection lets you transfer data using the more secure HTTPS protocol rather than the simpler HTTP protocol. Security over the Internet is a complex and delicate matter, however, and well beyond the scope of this book.
The remaining property and the only method of the Request object are almost always used together. The TotalBytes property returns the total number of bytes received from the client as a result of a POST method, and the BinaryRead method performs a low-level access to the raw data sent by the client:
<% bytes = Request.TotalBytes rowData = Request.BinaryRead(bytes) %> |
The BinaryRead method can't be used with the Form collection, so you have to choose one or the other. In practice, you won't often use this method.
The Response object represents the data being sent from the Web server to the client browser. It exposes five properties (Expires, ContentType, CharSet, Status, and Pics), four methods (Write, BinaryWrite, IsClientConnected, and AppendToLog) and one collection (Cookies).
The Write method of the Response object sends a string or an expression right to the client's browser. This method isn't strictly necessary because you can always use the <%= delimiter. For example, the following three statements are equivalent:
<B>Current Time is <% = Time %> </B> <% = "<B>Current Time is " & Time & "</B>" %> <% Response.Write "<B>Current Time is " & Time & "</B>" %> |
Choosing one over another is mostly a matter of programming style. When you're already inside a server-side script block, however, using the Response.Write method often delivers more readable code. at least to Visual Basic programmers.
The Buffer property gives you control over when the browser receives the page. The default for this property is False, which means that the browser receives the page while it's being built. If you set this property to True, all the data the ASP page produces is stored in a buffer and then sent to the client as a block of data when you invoke the Response object's Flush method. This buffering capability has a couple of advantages. First, in some cases, your ASP page will appear to be faster than it really is. Second, and most important, at any moment you can decide to discard what you've produced by using a Response.Clear method. For example, you might have a database query that sends output to the buffer; at the end of the process, you can check to find out whether an error has occurred and cancel the results built so far:
<% ' Here you execute the query. If Err Then Response.Clear Response.Write "An error has occurred" Else ' Send the result of the query to the client. Response.Flush End If %> |
You can also use the Response object's End method, which stops processing the ASP page and returns the page built so far to the browser.
The Response object also supports another method for sending data to a client, the BinaryWrite method. This method is rarely used, though. One situation in which you would use it is when you're sending nonstring data to a custom application.
The Response object's Cookies property (unlike that of the Request object) lets you create and modify the value of a cookie. The Cookies collection behaves much like a Dictionary object, so you can create a new cookie simply by assigning a new value to this property:
<% ' Remember the user's login name. Response.Cookies("LoginName") = Request.Form("txtLoginName") %> |
As I explained in the section "The Cookies Collection" earlier in the chapter, a cookie can have multiple values. For example, an Internet shopping site might have a shopping bag in which you can put multiple values:
<% ' Add a new item to the Bag cookie. product = Request.Form("txtProduct") quantity = Request.Form("txtQty") Response.Cookies("Bag")(product) = quantity %> |
Remember that cookies are sent back to the client in the HTTP header, and for this reason they must be assigned before sending the very first line of text to the client. otherwise, an error will occur. When it's impractical to assign a cookie before sending the first line of HTML text. and it often is. you can activate buffering before sending any text to the client. By default, cookies are stored on the client's machine only during the current session and are deleted when the browser is closed. You can change this default behavior by assigning a value to the Expires property of the cookie, as in the following code snippet:
<% ' This cookie is valid until December 31, 1999. Response.Cookies("LoginName").Expires = #12/31/1999# %> |
The Cookie object has other important properties as well. You can set the Domain property to a specific domain so that only the pages in that domain will receive that particular cookie. The Path property lets you be even more selective and decide that only the pages in a given path in that domain receive the cookie. Finally, the Secure Boolean property lets you specify whether the cookie should be transmitted exclusively over a Secure Sockets Layer (SSL) connection. Here's an example that uses all the properties of the Cookie object:
<% ' Create a secure cookie that expires after one year and that ' is valid only on the /Members path of the vb2themax.com site. Response.Cookies("Password") = Request.Form("txtPassword") Response.Cookies("Password").Expires = Now() + 365 Response.Cookies("Password").Domain = "/vb2themax.com" Response.Cookies("Password").Path = "/members" Response.Cookies("Password").Secure = True %> |
A final example demonstrates how a page can keep track of whether or not the user has already visited the site and also shows how an ASP page can query the user for missing values and then take the control again:
<% ' We're going to create cookies, so we need to turn on buffering. Response.Buffer = True %> <HTML> <HEAD></HEAD> <BODY> <H1>Using Cookies to manage login forms</H1> <% If Request.Cookies("LoginName") <> "" Then ' This isn't the first time the user has visited this site. %> It's nice to hear from you again, <% = Request.Cookies("LoginName") %>. <% Elseif Request.Form("txtLoginName") <> "" Then ' This is the user's first visit to this site, and ' she has just filled out the login form. ' Save the user's login name as a cookie for subsequent sessions. Response.Cookies("LoginName") = Request.Form("txtLoginName") Response.Cookies("LoginName").expires = Now() + 365 %> Welcome to this site, <% = Request.Form("txtLoginName") %> <% Else ' This is the user's first visit to this site, ' so prepare a login form. %> This is the first time you've logged in. Please enter your name:<P> <% url = Request.ServerVariables("URL")%> <FORM ACTION="<%= url %>" METHOD=POST NAME=form1> <INPUT TYPE="text" NAME=txtLoginName> <INPUT TYPE="submit" VALUE="Submit" NAME=submit1> </FORM> <% End If %> </BODY> </HTML> |
The preceding page can work in three different ways. The first time this page is accessed, it sends back an HTML form that asks for the user's name, as shown in the top portion of Figure 20-8. This information is then reposted to the page. notice how it builds the ACTION attribute of the <FORM> tag. which retrieves the contents of the txtLoginName control and stores it in a cookie that expires after one year. (See the middle part of Figure 20-8.) Finally, when the user navigates again to this page, the server is able to recognize the user and display a different "Welcome back" greeting. (See the bottom part of Figure 20-8.)
Figure 20-8. An ASP script can behave differently, according to whether a cookie is stored on the client side.
Some properties of the Response object let you control important attributes of the page sent back to the client. All these attributes are transmitted to the client in the HTTP header and therefore must be set before sending the contents of the page (or you have to activate the page buffering).
The expires property determines how many minutes the returned page can be held in the client browser's local cache. For example, if your page contains stock exchange data, you might want to set a relatively short expiration timeout:
<% ' This page expires after 5 minutes. Response.Expires = 5 %> |
In other circumstances, you might need to set an absolute expiration date and time, which you do through the ExpiresAbsolute property:
<% ' This page expires 1 minute before the year 2000. Response.expiresabsolute = #12/31/1999 23:59:00# %> |
You can decide whether the page can be held in a proxy server's cache by setting the CacheControl property to public; you set it to private to disable this kind of caching.
In most cases, the ASP code sends back HTML text to the client browser, but this isn't a requirement. In fact, you can produce any MIME (Multipurpose Internet Mail Extensions) contents the browser supports, provided that you inform the client what's going to arrive. You notify the browser by assigning a suitable value to the ContentType property, such as text/plain for plain text or text/richtext for MIME Rich Text format. You can also send back data in binary format, such as image/tiff or image/gif for an image extracted from a database. (If you use a binary format, you need to send the data using the BinaryWrite method.)
The CharSet property lets the ASP code inform the client browser that the text uses a given character set. The value of this property instructs the browser to use the right code page and ensures that the characters are displayed correctly on the client's screen. For example, to use the Greek code page, you execute this statement at the beginning of the page:
<% Response.CharSet = "windows-1253" %> |
The properties listed so far allow you to set some of the data that is sent to the client in the HTTP header. If you want to, you can modify any standard header information and even create custom header data, using the AddHeader method.
You can redirect the browser to another page using the redirect method. A typical use for this method is to redirect the browser to a particular page after reading one or more of the values stored in its ServerVariables collection. For example, a company that has international customers can prepare multiple home pages, one for each language, and automatically redirect the (potential) customer to the proper page:
<% If InStr(Request.ServerVariables("HTTP_ACCEPT_LANGUAGE"), "it") Then ' If the browser supports Italian, go to a specific page. Response.Redirect "Italy.asp" Else ' If the browser doesn't support Italian use the standard page. Response.Redirect "English.asp" End If %> |
You must use the Redirect method before sending any contents because redirection is achieved by sending an HTTP header back to the browser.
The Response object has two properties and two methods remaining: the Status and Pics properties and the IsClientConnected and the AppendToLog methods. The Status property sets or returns the HTTP status line returned to the browser. For example, it returns 200 OK if everything is correct or 404 Page not found if the page requested isn't available. The Pics property lets you add a value to the PICS-Label field in the HTTP header, which in turn lets the browser filter out pages whose content doesn't conform to the rules set in the browser's Content Advisor dialog box.
The IsClientConnected function returns False if the client has disconnected after the most recent Response.Write operation. This property is useful if the server-side script is engaged in a lengthy operation and you want to ensure that the client is still connected and waiting for an answer provided that logging has been enabled in IISO. Finally, the AppendToLog method writes a string to the Web Server log file; because this log file is comma-delimited, you shouldn't use commas in the string passed as an argument:
<% Response.AppendToLog "This page was loaded at " & Now() %> |
As you can deduce from its name, the Server object represents the Web server application. Although it has just one property (ScriptTimeout) and four methods (CreateObject, HTMLEncode, URLEncode, and MapPath), but the Server object plays a key role in most ASP applications.
The ScriptTimeout property sets or returns the number of seconds after which the server-side script is forced to terminate and an error is returned to the client. This property, whose default value is 90 seconds, is very useful to terminate extremely long queries or programming errors such as infinite loops.
You've already seen the Server object's CreateObject method in action. Recall that this method permits you to instantiate an external COM object, either from a standard library such as ADODB or from a custom component you've written yourself or bought from a third-party vendor. This method is especially useful with the Scripting library to create a FileSystemObject or a Dictionary object to make up for a few deficiencies of the VBScript language. (For example, VBScript doesn't support collections or file I/O statements.) Here's an interesting example of an ASP page that dynamically builds a list of hyperlinks to all the other HTM documents in its directory:
<H1>CreateObject demo</H1> This page demonstrates how you can use a FileSystemObject object to dynamically create hyperlinks to all the other pages in this directory.<P> <% Set fso = Server.CreateObject("Scripting.FileSystemObject") ' Get a reference to the folder that contains this file. aspPath = Request.ServerVariables("PATH_TRANSLATED") Set fld = fso.GetFile(aspPath).ParentFolder ' For each file in this folder, create a hyperlink. For Each file In fld.Files Select Case UCase(fso.GetExtensionName(file)) Case "HTM", "HTML" Response.Write "<A href=""" & file & """>" & file & "</A><BR>" End Select Next %> |
Don't forget that the object being created must be correctly registered on the machine the Web server application is running on. It's a good idea to protect all the Server.CreateObject methods with an On Error Resume Next statement.
Even if you create objects in ASP scripts following a method similar to the one you follow under Visual Basic or VBScript, you must pay attention to an important difference: The scope of all such objects is the page, and the objects are released only when the page has been completely processed. This means that setting an object variable to Nothing won't destroy the object because ASP retails a reference to the object and therefore keeps the object alive. This may have many nasty consequences. Take for example the following code:
Dim rs Set rs = Server.CreateObject("ADODB.Recordset") rs.Open "Authors", "DSN=Pubs" ... Set rs = Nothing |
In regular Visual Basic or VBScript, the last statement would close the Recordset and release all the associated resources. In an ASP script, however, this doesn't happen, unless you explicitly close the Recordset:
rs.Close Set rs = Nothing |
As you know, HTML uses angle brackets as special characters for defining tags. Although this way of encoding information is simple, it can cause problems when you're sending data read from somewhere else, such as from a database or a text file. In fact, any < character found in the database field would make the browser mistakenly believe that an HTML tag is arriving. The easiest way to work around this issue is by resorting to the Server object's HTMLEncode method, which takes a string and returns the corresponding HTML code that makes that string appear in the browser as-is. The following code example relies on this method to display values from a hypothetical database of math formulas (which are highly likely to contain special symbols):
<% Set rs = Server.CreateObject("ADODB.Recordset") rs.Open "SELECT * FROM Formulas", "DSN=MathDB" Do Until rs.EOF Response.Write Server.HTMLEncode(rs("Formula")) & "<BR>" Loop %> |
This method is also useful when you want to show HTML code in a page rather than having it interpreted by the browser. For example, the following ASP code displays the contents of the htmltext variable without interpreting the HTML tags it contains:
This is the typical beginning of an HTML page<P> <% htmltext = "<HTML><BODY>" Response.Write Server.HTMLEncode(htmltext) %> |
This is what is actually sent to the client, as you can see by using the View command from the Source menu of the browser:
This is the typical beginning of an HTML page<P> <HTML><BODY;><P> |
It's then rendered into this in the brower's window:
This is the typical beginning of an HTML page <HTML><BODY> |
Sending the special <% and %> pair of characters is a bit more complex because these characters confuse the ASP script parser. For example, say that you want to send the following string to the browser:
<% Set obj = Nothing %> |
Unfortunately, you can't simply use the following ASP script because it raises an error, "Unterminated string constant":
<% ' CAUTION: This doesn't work! Response.Write Server.HTMLEncode("<% Set obj = Nothing %>") %> |
One way to avoid this problem is to keep the two characters of the %> pair apart, using the concatenation operator:
<% ' This works! Response.Write Server.HTMLEncode("<% Set obj = Nothing %" & ">") %> |
Another solution is to use the backslash (\) character to inform the ASP script that the character that comes next is to be taken literally:
<% ' This works too! Response.Write Server.HTMLEncode("<% Set obj = Nothing %\>") %> |
The URLEncode method lets you solve similar problems caused by the unusual way in which HTML formats URLs. We first encountered this problem in the section "Sending Data to the Server", when we were building client-side scripts that use the Window.Navigate method to open a new page and send it additional values via its URL. Sometimes you also have to solve this problem when you're writing server-side scripts. but in this case, the solution is simple. For example, you can dynamically create a hyperlink that jumps to another page and passes the contents of a database field via the URL:
<% ' This code assumes that rs contains a reference to an open Recordset.DoUntilrs.EOF Response.Write "<A href=""select.asp?name=" Response.Write Server.URLEncode(rs("Name")) & """>" Response.Write rs("Name") & "</A></BR>" rs.MoveNext Loop %> |
The final method of the Server object is MapPath, which converts a logical path as seen by the client browser into a physical path on the server machine. If the argument passed to this method has a leading / or \ character, the path is considered to be absolute with respect to the application's root directory; without such a leading character, it is considered to be relative to the directory where the current ASP document is. For example, the following code redirects the browser to the default.asp page located in the root directory:
<% Response.Redirect Server.MapPath("\default.asp") %> |
This code redirects the browser to the two.asp page in the same directory where the current page is:
<% Response.Redirect Server.MapPath("two.asp") %> |
You can determine the name of the root directory, the current directory, and the parent directory using the following code:
<% rootDir = Server.MapPath("\") curDir = Server.MapPath(".") parentDir = Server.MapPath("..") %> |
The Application object represents the Web server application running inside IIS. This is a global object that is shared by all the clients that are accessing one of the pages that make up the application in a given moment. An Application object comes to life when the first client accesses one of its pages and ends only when the administrator stops the Web server or when the server machine crashes. If the Application object runs in its own address space, you can also terminate it by clicking the Unload button in the Directory tab of the Application object's Property Pages dialog box.
The Application object has a rather simple interface, with just one property (Value), two collections (Contents and StaticObjects), and two methods (Lock and Unlock). Unlike all the other objects examined so far (but similar to the Session object, which is described in "Session Events"), the Application object exposes events (OnStart, OnEnd).
The main use for the Application object is storing data that should be available to all the scripts that are serving client requests in a given moment. Examples of such data include the location of a database file or a flag that indicates whether a given resource is accessible. Such shared data is available through the Application's object Value property, which expects the name of the variable you want to read or set. Because this is the default property of the Application object, in most cases it is omitted:
<% ' Increment a global counter. (WARNING: This might not work correctly.) Application("GlobalCounter") = Application("GlobalCounter") + 1 %> |
This code snippet has a problem, though. Because the Application object is shared among all the ASP scripts that are serving the currently connected clients , a server-side script might execute the same statement in exactly the same moment as another script. This would cause incorrect values for the Application variable. To avoid such undesirable situations, any time you're going to access one or a group of Application object's variables, you should bracket the code within a Lock and Unlock pair of methods:
<% ' Increment a global counter. (This code always works correctly.) Application.Lock Application("GlobalCounter") = Application("GlobalCounter") + 1 Application.Unlock %> |
When you use this approach, only one script can execute the code in the critical section between the two methods. The second script that encounters the Lock method will patiently wait until the first script executes the Unlock method. It goes without saying that you should avoid inserting any lengthy operation after the Lock method and that you should invoke the Unlock method as soon as possible.
CAUTION
In general, you should invoke the Lock and Unlock methods even when just reading an Application object's variables. This precaution might seem excessive, but remember that under Windows NT and Windows 2000 Server a thread can be preempted at any time. When you're dealing with variables that hold objects, locking is even more important because many properties and methods of the object might not be reentrant.
The onstart and onend events of the Application object fire. not surprisingly. when the Web application starts and ends, respectively. The problem with these events is that when the Application object is created, no ASP document is active yet. For this reason, the code for these events must be located in a special file, named Global.asa, that must reside in the root directory of the application. Here's an example of a Global.asa file that records the time the application starts:
<SCRIPT LANGUAGE=vbscript RUNAT=Server> Sub Application_OnStart() Application("StartTime") = Now() End Sub </SCRIPT> |
NOTE
The events in the Global.asa file fire only when the client accesses an ASP file. The events don't fire when an HTM or HTML file is read.
You can't use the <% and %> delimiters in the Global.asa. The only valid way to insert VBScript code is by using the <SCRIPT RUNAT=Server> tag. OnStart events are often useful for creating an instance of an object when the application starts so that individual scripts don't need to invoke a Server.CreateObject method. For example, you might create an instance of the FileSystemObject class and use it inside ASP scripts, which will speed up the execution of individual scripts:
<SCRIPT LANGUAGE=vbscript RUNAT=Server> Sub Application_OnStart() Set fso = Server.CreateObject("Scripting.FileSystemObject") ' This is an object, so we need a Set command. Set Application("FSO") = fso End Sub </SCRIPT> |
The OnEnd event is less useful than the OnStart event. You typically use it to release resources, for example, to close a connection opened in the OnStart event. Another common use is to permanently store on a database the value of a global variable (such as a counter that keeps track of how many visitors a given page has had) that you don't want to reinitialize the next time the application is started.
CAUTION
Because the Global.asa file must reside in the home directory of an application, two distinct applications share the same Global.asa file if they share the home directory. Needless to say, this can be the source of countless problems. For example, if you modify the Global.asa for one application, the other one is also affected. I strongly advise you to assign a different home directory to each application.
Because the Value property isn't a collection, you can't enumerate all the Application object's variables. However, the Application object does provide you with two collections that let you retrieve information about global values available to all the scripts.
The Contents collection contains references to all the elements that have been added to the application object via a script and can be accessed by ASP code. This collection includes simple values and objects created with the Server.CreateObject method. You can therefore enumerate all the Application's variables using the following code:
<% For Each item In Application.Contents If IsObject(Application.Contents(item)) Then objClass = TypeName(Application.Contents(item)) Response.Write item & " = object of class " & objClass Else Response.Write item & " = " & Application.Contents(item) End If Next %> |
The StaticObjects collection is similar to the Contents collection, but it contains only the objects that have been created with an <OBJECT> tab in the Application's scope. Because you're guaranteed that this collection contains only objects, you can iterate through the objects using a simple loop:
<% For Each item In Application.StaticObjects objClass = TypeName(Application.StaticObjects(item)) Response.Write item & " = object of class " & objClass Next %> |
The Session object represents the connection between a given client and the Web server. Whenever a client accesses one of the ASP documents on the server, a new Session object is created and is given a unique ID. This object will live as long as the client keeps the browser open. By default, it's destroyed when the server doesn't receive a request from that client within a given timeout interval. The default for this timeout is 20 minutes, but you can change it by assigning a new value to the Session's Timeout property. For example, you might want to reduce this value if your Web site is receiving too many requests and you want to release the resources allocated to users as often as possible:
<% ' Reduce the timeout to 10 minutes. Session.Timeout = 10 %> |
The Session object resembles the Application object in that both expose the Contents and the StaticObjects collections, the Value property, and the OnStart and OnEnd events. The main difference between the two objects is that IIS creates only one Application object for each application but as many Session objects as the number of users that are connected in a given moment.
The Session object's Value property lets you store and retrieve values with a session scope, which means that only the pages requested by a given client can share a particular set of values. The difference between variables with application scope and those with session scope is illustrated in Figure 20-9.
Figure 20-9. Application variables and session variables.
Session variables are especially important because all the variables declared in an ASP script are destroyed when the browser jumps to another page. Session variables bring state management to the stateless HTTP protocol. At first, it might seem impossible for the Web browser to keep information pertinent to one user distinct from the information belonging to another user because the protocol doesn't even maintain information about who's making the request. The solution to this problem is provided by a special cookie that the Web server sends to the client browser and that the browser sends back to the server at each subsequent request. Because this cookie has no specific expiration date, it expires when the browser is closed or after the timeout. You can also force the Session object to terminate using its Abandon method.
You reference Session object's variables by using the Value property, which is the default property for this object and therefore can be omitted:
<% ' Remember the user's name while the session is open. Session("UserName") = Request.Form("txtUserName") %> |
Session objects are great, but they have an adverse impact on the performance and the scalability of the application. Each Session object consumes resources on the server, and some session operations are serialized, so each session must wait for its turn. You can minimize these problems by creating Session objects only if you really need them. You can limit the number of Session objects created by adding the following line at the beginning of all your ASP scripts:
<%@ EnableSessionState = False %> |
Alternatively, you can disable Session variables for the ASP subsytem by changing the AllowSessionState value of the HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W3SVC\ASP\Parameters Registry key from 1 to 0.
When a client accesses a page that doesn't support session state, no Session object is created and the Session_OnStart event procedure isn't executed. This means that you can't store values in the Session object any longer and that you must implement your own persistence scheme, for example, one based on a real, multiuser database.
As I mentioned in the previous section, the Session object exposes the OnStart and OnEnd events. As is the case with the Application object, because you write the code for these events in the Global.asa file, all the Sessions objects share the same event procedures. Typically, you use the OnStart event to create a resource that is to be used during the entire session, such as an ADO Connection object. The following code demonstrates how you can make the Application and Session objects cooperate:
<SCRIPT LANGUAGE=vbscript RUNAT=Server> Sub Application_OnStart() ' Initialize the Connect string that points to the shared database. ' In a real application, the string might be read from an INI file. conn = "Provider=SQLOLEDB;Data Source= MySrv;UserID=sa;Password=MyPwd" Application("ConnString") = str End Sub Sub Session_OnStart() Set cn = Server.CreateObject("ADODB.Connection") cn.Open Application("ConnString") ' Make this Connection object available to all the ASP scripts ' in the session. Set Session("Connection") = cn End Sub Sub Session_OnEnd() ' Release all the resources in an orderly way. Set cn = Session("Connection") cn.Close Set cn = Nothing End Sub </SCRIPT> |
Here's another example that uses the OnStart and OnEnd events to keep track of how many sessions are currently active:
<SCRIPT LANGUAGE=vbscript RUNAT=Server> Sub Application_OnStart() Application("SessionCount") = 0 End Sub Sub Session_OnStart() Application.Lock Application("SessionCount") = Application("SessionCount") + 1 Application.Unlock End Sub Sub Session_OnEnd() Application.Lock Application("SessionCount") = Application("SessionCount") - 1 Application.Unlock End Sub </SCRIPT> |
The Session object exposes two properties that permit you to create Web sites that can accommodate international users. The LCID property sets or returns the locale ID that will be used when sorting and comparing strings, and for all the date-related and time-related functions. For example, the following code snippet displays the current date and time using the Italian format and then restores the original locale ID.
<% currLocaleID = Session.LCID ' The locale ID of Italy is hex 410. Session.LCID = &H410 Response.Write "Current date/time is " & Now() ' Restore the original locale ID. Session.LCID = currLocaleID %> |
The other property that lets you add an international flavor to a Web site is CodePage, which sets or returns the code page used when reading text from or writing text to the browser. For example, most Western languages use code page 1252, and Hebrew uses code page 1255.
The Session object exposes the Contents and StaticObjects collections in the same way that the Application object does, so I won't describe those collections again here. (See the section: "The Contents and StaticObjects Collections" earlier in this chapter for details about these collections.) These collections contain only elements with a session scope, which raises an interesting question: How can you have a session-scoped <OBJECT> tag in the Global.asa file? The answer is in the SCOPE attribute:
<OBJECT RUNAT=Server SCOPE=Session ID=Conn ProgID="ADODB. Connection"> |
The sixth and final object in the ASP object model is the ObjectContext object. This object is used only when an ASP script runs in a transaction and is managed by Microsoft Transaction Server. Such transaction pages contain a <%@ TRANSACTION %> directive at the beginning of the script.
The ObjectContext object exposes two methods, SetComplete and SetAbort, which commit and abort the transaction, respectively. It also exposes two events, OnTransactionCommit and OnTransactionAbort, which fire after a transaction is completed successfully or is aborted, respectively. Because this book doesn't cover MTS programming, I won't explain these methods and events in detail.
NOTE
As I'm writing this, Internet Information Server 5.0 is in beta, so it's possible to get an idea of the new features of ASP technology, although the final release may still change some of the details. The Server object has been enhanced with three new methods. The Execute method executes another ASP document and then returns to the current ASP script. The Transfer method is similar to the Response.Redirect method but is more efficient because no data is sent to the client browser. The GetLastError method returns a reference to the new ASPError object, which contains detailed information about errors. The Application and Session's Contents collections have been improved with the Remove and RemoveAll methods, which let you delete one or all the elements of the collection. Finally, the ASP parser is more efficient, so pages will be processed faster.